import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

//      泛型

//  在编译的过程当中，将所有的T替换为Object这种机制，我们称为：擦除机制。
//  Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。
//  什么是泛型的上界？
//  class 泛型类名称<类型形参 extends 类型边界> {   ...}   如果没有指定边界 默认是Object
//  泛型没有下界
//  1. 泛型是将数据类型参数化，进行传递
//  2. 使用<T>表示当前类是一个泛型类。
//  3. 泛型目前为止的优点：数据类型参数化，编译时自动进行类型检查和转换


//      通配符（课件）
//  ? 用于在泛型的使用，即为通配符
//
//  通配符是用来解决泛型无法协变的问题的，协变指的就是如果Student是Person的子类，
//  那么List<Student> 也应该是List<Person> 的子类。但是泛型是不支持这样的父子类关系的。
//  1、泛型T是确定的类型，一旦你传了我就定下来了，而通配符则更为灵活或者说是不确定，更多的是用于扩充参数的范围.
//  2、或者我们可以这样理解：泛型T就像是个变量，等着你将来传一个具体的类型，而通配符则是一种规定，规定你能传哪些参数。

//  通配符上界？ extends
//  <? extends 上界>
//  <? extends Number>//可以传入的实参类型是Number或者Number的子类

//  通配符下界？ super
//  <? super 下界>
//  <? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型









//      object是所有类的父类
class MyArray1 {
    public Object[] objects = new Object[10];

    public void set(int pos,Object val) {
        objects[pos] = val;
    }
    public Object get(int pos) {
        return objects[pos];
    }
}

/**
 *
 * @param <T>   此时代表当前类是一个泛型类  T：当做是一个占位符
 */
class MyArray<T> {
//    public T[] objects = new T[10];//error  不能实例化泛型数组   T[10]这里T报红
    public T[] objects = (T[])new Object[10];//这样写不报错  但是不是非常好  正确的做法是通过反射
//      我们该如何写呢

    public void set(int pos,T val) {
        objects[pos] = val;
    }
    public T get(int pos) {
        return objects[pos];
    }


    //Object[]-> 可以放任意类型   "hello"   123   double
    public T[] getArray() {
        return objects;
    }
}




//  写一个泛型类 求出数组当中的最大值

//          此时传入的T 一定要实现这个接口  也是泛型的上界
//          是T实现这个（Comparable）接口 不是这个类实现这个（Comparable）接口
class Alg<T extends Comparable<T>> {//这里的extends是拓展的意思  不要理解成继承
    public T findMax(T[] array) {
        if (array == null) return null;
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            //if括号内为什么会报错  因为泛型传入都是引用类型 不能通过><=!号来比较
//            if (max < array[i]) {
//            解决方法 指定一个特殊的上界

            if (max.compareTo(array[i]) < 0) {
                max = array[i];
            }
        }
        return max;
    }
}


//  方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
//  可否直接通过类名调用 不用new一个对象  添加static
//  因为静态方法不依赖于对象 但是我们要使用compareTo得拓展Comparable<T>接口
//  就是  static<T extends Comparable<T>>
class Alg2{
    public static<T extends Comparable<T>> T findMax(T[] array) {
        if (array == null) return null;
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            if (max.compareTo(array[i]) < 0) {
                max = array[i];
            }
        }
        return max;
    }
}






//  通配符
class Alg3 {
//    都是传任何类型

//    但是这里是我能确定T是啥类型
    public static <T> void print(ArrayList<T> list) {
        for (T x :list) {
            System.out.println(x);
        }
    }
//     这里我们不知道   ？  是什么类型 所以我们只能通过Object来接收
    public static <T> void print2(ArrayList<?> list) {
        for (Object x :list) {
            System.out.println(x);
        }
    }
}





//  通配符下界
class A {
}
class Person extends A{
}
class Student extends Person {
}
class c extends Student{
}






public class TestDemo {
    public static void main1(String[] args) {
        MyArray1 myArray = new MyArray1();
        myArray.set(0,"hello");
        myArray.set(1,123);
        String str = (String) myArray.get(0);//需要进行强制类型转换  或者用Object接收
        Object str2 = myArray.get(1);
//        如何解决 加尖括号
    }


    public static void main2(String[] args) {
        MyArray<String> myArray = new MyArray<>();
//        MyArray<int> myArray2 = new MyArray<>();基本类型不能作为泛型类的参数
        myArray.set(0,"hello");
        myArray.set(1,"123");
//        myArray.set(2,123);//error  1.编译的时候 自动进行类型的检查
        String str = myArray.get(0);//2.不需要进行类型的强制转换 编译器会自动帮我们进行类型的转换
    }


    public static void main3(String[] args) {
        MyArray<String> myArray = new MyArray<>();
        String[] str = myArray.getArray();//运行时 报错
//        编译器认为此时并不是很安全   不确定该objects类型
//        使用我们不能直接new 一个T[] 类型数组
    }

    public static void main4(String[] args) {
        Alg<Integer> alg = new Alg<>();
        Integer[] array = {1,22,3,4};
        System.out.println(alg.findMax(array));//22

        Alg<String> alg2 = new Alg<>();
        String[] array2 = {"hello","world","cat"};
        System.out.println(alg2.findMax(array2));//world
//        也是可以比较的 为什么呢  因为String也实现了Comparable接口
    }


//    Alg2不需要调用对象
    public static void main5(String[] args) {
        Integer[] array = {1,22,3,4};
        System.out.println(Alg2.findMax(array));//22
        System.out.println(Alg2.<Integer>findMax(array));//22
//        它可以通过array类型推导出是那种类型  使用<Integer>也可以不用写
    }


    public static void main6(String[] args) {
        Alg<Integer> alg1 = new Alg<>();
        System.out.println(alg1);//Alg@1b6d3586
        Alg<Integer> alg2 = new Alg<>();
        System.out.println(alg2);//Alg@4554617c
//        类型都是Alg  在JVM中没有泛型概念 在编译时这系列的类型会被擦除

//        所以
//         public class MyArrayList<E> { ... }
//         MyArrayList<Object> 不是 MyArrayList<Number> 的父类型
//         MyArrayList<Number> 也不是 MyArrayList<Integer> 的父类型
    }


    public static void main7(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        list1.add(1);
        list1.add(2);
        Alg3.print(list1);//1  2
        Alg3.print2(list1);//1  2
    }



//    通配符的上界-特点  读
//      不适合写入数据  适合读取数据

//         需要使用通配符来确定父子类型
    public static void main8(String[] args) {
        ArrayList<Integer> arrayList1 = new ArrayList<>();
        ArrayList<Double> arrayList2 = new ArrayList<>();
        List<? extends Number> list = arrayList1;
        List<? extends Number> list2 = arrayList2;
//        list.add(0,12);error   引用的类型很多 编译器不知道该引用什么类型
//        list.add(1,12.2);error
//        此时的list可以引用的子类对象很多 编译器无法确定具体的类型 编译器为了安全起见 此时只允许你进行读取
//        所以对于通配符的上界 不适合写入数据  适合读取数据

        Number o = list.get(0);
//        Integer a = list.get(1);error  引入的子类太多 不确定类型
    }


//    通配符下界   写
//    不允许进行读取
//通配符下界
//    可以传入类型实参是 Integer 父类的任意类型的 MyArrayList
    public static void printAll(MyArray<? super Integer> list) {
    }
    public static void main9(String[] args) {
        printAll(new MyArray<Integer>());
        printAll(new MyArray<Number>());
        printAll(new MyArray<Object>());
    }


    public static void main(String[] args) {
        ArrayList<? super Person> arrayList = new ArrayList<Person>();
//        ArrayList<? super Person> arrayList2 = new ArrayList<Student>();error
//        只能引用 Person或者Person的子类

//        arrayList.add(new A());error  只能引用 Person或者Person的子类
        arrayList.add(new Person());
        arrayList.add(new Student());
        arrayList.add(new c());
//        因为添加元素的时候，我们知道list引用的对象肯定是Person或者Person的父类的集合，
//        我们能够确定此时存储的元素的最小粒度比Person小的都可以。



//        Person person = arrayList.get(0);error  why? 作业
//        Student student = arrayList.get(0);error  why? Person的子类很多 不确定读取到啥
//        但是，你读取的时候，你知道是读取到的是哪个子类吗？
//        可以通过Object接收
        Object o = arrayList.get(0);
    }


}
